﻿/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Text;
using System.Globalization;
using STSdb.General.Buffers;

namespace STSdb.Data
{
    /// <summary>
    /// Set one-to-one correspondence between TKey and byte array.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    public interface IKeyMap<TKey>
    {
        /// <summary>
        /// Converts TKey to byte array.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        byte[] Direct(TKey key);
        /// <summary>
        /// Converts byte array to TKey.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        TKey Reverse(byte[] index);
    }

    /// <summary>
    /// Descending implementation of IKeyMap.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public class DescendingKeyMap<T> : IKeyMap<T>
    {
        public IKeyMap<T> InternalKeyMap;

        public DescendingKeyMap()
            : this(null)
        {
        }

        public DescendingKeyMap(IKeyMap<T> internalKeyMap)
        {
            InternalKeyMap = internalKeyMap;
        }

        #region IKeyMap<T> Members

        public byte[] Direct(T key)
        {
            byte[] index = InternalKeyMap.Direct(key);
            byte[] buffer = (byte[])index.Clone();
            for (int i = 0; i < buffer.Length; i++)
                buffer[i] = (byte)(byte.MaxValue - buffer[i]);

            return buffer;
        }

        public T Reverse(byte[] index)
        {
            byte[] buffer = (byte[])index.Clone();
            for (int i = 0; i < buffer.Length; i++)
                buffer[i] = (byte)(byte.MaxValue - buffer[i]);

            return InternalKeyMap.Reverse(buffer);
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for Byte[]
    /// </summary>
    [Serializable]
    public class ByteArrayKeyMap : IKeyMap<byte[]>
    {
        #region IKeyMap<byte[]> Members

        public byte[] Direct(byte[] key)
        {
            return key;
        }

        public byte[] Reverse(byte[] index)
        {
            return index;
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for Byte
    /// </summary>
    [Serializable]
    public class ByteKeyMap : IKeyMap<Byte>
    {
        #region IKeyMap<byte> Members

        public byte[] Direct(byte key)
        {
            return new byte[] { key };
        }

        public byte Reverse(byte[] index)
        {
            return index[0];
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for SByte
    /// </summary>
    [Serializable]
    public class SByteKeyMap : IKeyMap<SByte>
    {
        private readonly ByteKeyMap internalKeyMap = new ByteKeyMap();

        #region IKeyMap<sbyte> Members

        public byte[] Direct(sbyte key)
        {
            Byte val = (Byte)(key + SByte.MaxValue + 1);

            return internalKeyMap.Direct(val);
        }

        public sbyte Reverse(byte[] index)
        {
            Byte val = internalKeyMap.Reverse(index);

            return (SByte)(val - (Byte)SByte.MaxValue - 1);
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for Boolean
    /// </summary>
    public class BooleanKeyMap : IKeyMap<Boolean>
    {
        #region IKeyMap<bool> Members

        public byte[] Direct(bool key)
        {
            return new byte[] { key ? Byte.MaxValue : Byte.MinValue };
        }

        public bool Reverse(byte[] index)
        {
            return index[0] == Byte.MaxValue;
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for Char
    /// </summary>
    public class CharKeyMap : IKeyMap<Char>
    {
        private readonly ByteArrayKeyMap internalKeyMap = new ByteArrayKeyMap();

        #region IKeyMap<char> Members

        public byte[] Direct(char key)
        {
            byte[] buffer = BitConverter.GetBytes(key);

            return internalKeyMap.Direct(buffer);
        }

        public char Reverse(byte[] index)
        {
            byte[] buffer = internalKeyMap.Reverse(index);

            return BitConverter.ToChar(buffer, 0);
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for UInt16
    /// </summary>
    [Serializable]
    public class UInt16KeyMap : IKeyMap<UInt16>
    {
        #region IKeyMap<UInt16> Members

        public byte[] Direct(UInt16 key)
        {
            return BitConverter.GetBytes(key);
        }

        public UInt16 Reverse(byte[] index)
        {
            return BitConverter.ToUInt16(index, 0);
        }

        #endregion
    }

    /// <summary>
    /// IKeyMap implementation for Int16
    /// </summary>
    [Serializable]
    public class Int16KeyMap : IKeyMap<Int16>
    {
        private readonly UInt16KeyMap internalKeyMap = new UInt16KeyMap();

        #region IKeyMap<Int16> Members

        public byte[] Direct(Int16 key)
        {
            UInt16 val = (UInt16)(key + Int16.MaxValue + 1);

            return internalKeyMap.Direct(val);
        }

        public Int16 Reverse(byte[] index)
        {
            UInt16 val = internalKeyMap.Reverse(index);

            return (Int16)(val - (UInt16)Int16.MaxValue - 1);
        }

        #endregion
    }

    [Serializable]
    public class UInt32KeyMap : IKeyMap<UInt32>
    {
        #region IKeyMap<UInt32> Members

        public byte[] Direct(UInt32 key)
        {
            return BitConverter.GetBytes(key);
        }

        public UInt32 Reverse(byte[] index)
        {
            return BitConverter.ToUInt32(index, 0);
        }

        #endregion
    }

    [Serializable]
    public class Int32KeyMap : IKeyMap<Int32>
    {
        private readonly UInt32KeyMap internalKeyMap = new UInt32KeyMap();

        #region IKeyMap<Int32> Members

        public byte[] Direct(Int32 key)
        {
            UInt32 val = (UInt32)(key + Int32.MaxValue + 1);

            return internalKeyMap.Direct(val);
        }

        public Int32 Reverse(byte[] index)
        {
            UInt32 val = internalKeyMap.Reverse(index);

            return (Int32)(val - (UInt32)Int32.MaxValue - 1);
        }

        #endregion
    }

    [Serializable]
    public class UInt64KeyMap : IKeyMap<UInt64>
    {
        #region IKeyMap<UInt64> Members

        public byte[] Direct(UInt64 key)
        {
            // +5% insert speed
            // +4% read speed
            if (key <= UInt32.MaxValue) //optimization
                return BitConverter.GetBytes((UInt32)key);
            else
                return BitConverter.GetBytes(key);
        }

        public UInt64 Reverse(byte[] index)
        {
            if (index.Length == sizeof(UInt32))
                return BitConverter.ToUInt32(index, 0);
            else
                return BitConverter.ToUInt64(index, 0);
        }

        #endregion
    }

    [Serializable]
    public class FixedUInt64KeyMap : IKeyMap<UInt64>
    {
        #region IKeyMap<UInt64> Members

        public byte[] Direct(UInt64 key)
        {
            return BitConverter.GetBytes(key);
        }

        public UInt64 Reverse(byte[] index)
        {
            return BitConverter.ToUInt64(index, 0);
        }

        #endregion
    }

    [Serializable]
    public class Int64KeyMap : IKeyMap<Int64>
    {
        private readonly UInt64KeyMap internalKeyMap = new UInt64KeyMap();

        #region IKeyMap<Int64> Members

        public byte[] Direct(Int64 key)
        {
            UInt64 val = (UInt64)(key + Int64.MaxValue + 1);

            return internalKeyMap.Direct(val);
        }

        public Int64 Reverse(byte[] index)
        {
            UInt64 val = internalKeyMap.Reverse(index);

            return (Int64)(val - (UInt64)Int64.MaxValue - 1);
        }

        #endregion
    }

    [Serializable]
    public class DoubleKeyMap : IKeyMap<Double>
    {
        private readonly UInt64KeyMap internalKeyMap = new UInt64KeyMap();
        public readonly SortOrderMap SortOrder;

        public DoubleKeyMap(SortOrderMap sortOrder)
        {
            SortOrder = sortOrder;
        }

        public DoubleKeyMap()
            :this(SortOrderMap.Ascending)
        {
        }

        private byte[] InternalDirect(Double key)
        {
            long val = BitConverter.DoubleToInt64Bits(key);
            ulong uval = (ulong)val;

            if (key < 0)
                uval = ~uval;
            else
                uval |= 0x8000000000000000;

            return internalKeyMap.Direct(uval);
        }

        private Double InternalReverse(byte[] index)
        {
            ulong uval = internalKeyMap.Reverse(index);

            if ((uval & 0x8000000000000000) == 0)
                uval = ~uval;
            else
                uval &= ~0x8000000000000000;

            long val = (long)uval;

            return BitConverter.Int64BitsToDouble(val);
        }

        #region IKeyMap<Double> Members

        public byte[] Direct(Double key)
        {
            switch (SortOrder)
            {
                case SortOrderMap.Ascending: return InternalDirect(key);
                case SortOrderMap.Descending: return InternalDirect(Double.MaxValue - key);
                case SortOrderMap.None: return BitConverter.GetBytes(key);
                default:
                    throw new NotSupportedException(SortOrder.ToString());
            }
        }

        public Double Reverse(byte[] index)
        {
            switch (SortOrder)
            {
                case SortOrderMap.Ascending: return InternalReverse(index);
                case SortOrderMap.Descending: return Double.MaxValue - InternalReverse(index);
                case SortOrderMap.None: return BitConverter.ToDouble(index, 0);
                default:
                    throw new NotSupportedException(SortOrder.ToString());
            }
        }

        #endregion
    }

    [Serializable]
    public class SingleKeyMap : IKeyMap<Single>
    {
        private readonly DoubleKeyMap internalKeyMap;
        public SortOrderMap SortOrder { get { return internalKeyMap.SortOrder; } }

        public SingleKeyMap(SortOrderMap sortOrder)
        {
            internalKeyMap = new DoubleKeyMap(sortOrder);
        }

        public SingleKeyMap()
            :this(SortOrderMap.Ascending)
        {
        }

        #region IKeyMap<Single> Members

        public byte[] Direct(Single key)
        {
            return internalKeyMap.Direct((Double)key);
        }

        public Single Reverse(byte[] index)
        {
            return (Single)internalKeyMap.Reverse(index);
        }

        #endregion
    }

    [Serializable]
    public class DecimalKeyMap : IKeyMap<Decimal>
    {
        public readonly SortOrderMap SortOrder;

        public DecimalKeyMap(SortOrderMap sortOrder)
        {
            SortOrder = sortOrder;
        }

        public DecimalKeyMap()
            :this(SortOrderMap.Ascending)
        {
        }

        private byte[] ToArray(Decimal key)
        {
            int[] bits = Decimal.GetBits(key);
            byte[] buffer = new byte[4 * sizeof(int)];
            Buffer.BlockCopy(bits, 0, buffer, 0, 4 * sizeof(int));

            return buffer;
        }

        private Decimal FromArray(byte[] buffer)
        {
            int[] bits = new int[4];
            Buffer.BlockCopy(buffer, 0, bits, 0, 4 * sizeof(int));

            return new Decimal(bits);
        }

        private byte[] InternalDirect(Decimal key)
        {
            bool negative = key < 0;
            if (negative)
                key = Math.Abs(key);

            Decimal head = Decimal.Truncate(key);
            Decimal frac = key - head;

            int headLen = 0;
            byte[] headBuffer = new byte[15];
            while (head > 0m)
            {
                Decimal remainder = Decimal.Remainder(head, 100m);
                headBuffer[headLen++] = (byte)remainder;
                head = (head - remainder) / 100m;
            }

            int fracLen = 0;
            byte[] fracBuffer = new byte[15];
            while (frac > 0m)
            {
                frac = frac * 100m;
                Decimal tmp = Decimal.Truncate(frac);
                fracBuffer[fracLen++] = (byte)tmp;
                frac = frac - tmp;
            }

            byte[] buffer = new byte[15 + 1];
            int i = buffer.Length - 1;

            buffer[i--] = negative ? (byte)(100 - headLen) : (byte)(headLen | 0x80);
            for (int j = headLen - 1; j >= 0; j--)
                buffer[i--] = negative ? (byte)(100 - headBuffer[j]) : headBuffer[j];
            for (int j = 0; j < fracLen; j++)
                buffer[i--] = negative ? (byte)(100 - fracBuffer[j]) : fracBuffer[j];

            return buffer;
        }

        private Decimal InternalReverse(byte[] index)
        {
            Decimal key = 0m;

            byte flag = index[index.Length - 1];
            bool negative = flag < 0x80;
            int headLen = negative ? 100 - flag : flag & 0x7F;

            Decimal coef = 1m;
            for (int j = index.Length - 1 - headLen; j < index.Length - 1; )
            {
                Decimal head = coef * (negative ? 100 - index[j] : index[j]);
                key += head;

                j++;
                if (j >= index.Length - 1)
                    break;

                coef = 100m * coef;
            }

            int k = 0;
            while (index[k] == 0)
                k++;

            coef = 100m;
            for (int j = index.Length - 1 - headLen - 1; j >= k; )
            {
                Decimal frac = (negative ? 100 - index[j] : index[j]) / coef;
                key += frac;

                j--;
                if (j < k)
                    break;

                coef = 100m * coef;
            }

            if (negative)
                key = -key;

            return key;
        }
        
        #region IKeyMap<decimal> Members

        public byte[] Direct(Decimal key)
        {
            switch (SortOrder)
            {
                case SortOrderMap.Ascending: return InternalDirect(key);
                case SortOrderMap.Descending: return InternalDirect(Decimal.MaxValue - key);
                case SortOrderMap.None: return ToArray(key);
                default:
                    throw new NotSupportedException(SortOrder.ToString());
            }
        }

        public Decimal Reverse(byte[] index)
        {
            switch (SortOrder)
            {
                case SortOrderMap.Ascending: return InternalReverse(index);
                case SortOrderMap.Descending: return Decimal.MaxValue - InternalReverse(index);
                case SortOrderMap.None: return FromArray(index);
                default:
                    throw new NotSupportedException(SortOrder.ToString());
            }
        }

        #endregion
    }

    [Serializable]
    public class DateTimeKeyMap : IKeyMap<DateTime>
    {
        private readonly Int64KeyMap internalKeyMap = new Int64KeyMap();

        #region IKeyMap<DateTime> Members

        public byte[] Direct(DateTime key)
        {
            return internalKeyMap.Direct(key.Ticks);
        }

        public DateTime Reverse(byte[] index)
        {
            return new DateTime(internalKeyMap.Reverse(index));
        }

        #endregion
    }

    [Serializable]
    public class TimeSpanKeyMap : IKeyMap<TimeSpan>
    {
        private readonly Int64KeyMap internalKeyMap = new Int64KeyMap();

        #region IKeyMap<TimeSpan> Members

        public byte[] Direct(TimeSpan key)
        {
            return internalKeyMap.Direct(key.Ticks);
        }

        public TimeSpan Reverse(byte[] index)
        {
            return new TimeSpan(internalKeyMap.Reverse(index));
        }

        #endregion
    }

    [Serializable]
    public class StringKeyMap : IKeyMap<String>
    {
        public const int DEFAULT_MAX_LENGTH = 64;
        
        //bytes reserved for SortKey
        public readonly int MaxStringLength;
        public readonly int MaxLength;
        public readonly Encoding Encoding;
        public readonly CompareInfo CompareInfo;
        public readonly SortOrderMap SortOrder;

        public StringKeyMap(int maxStringLength, SortOrderMap sortOrder, CompareInfo compareInfo, Encoding encoding)
        {
            Encoding = encoding;
            SortOrder = sortOrder;
            CompareInfo = compareInfo;
            MaxStringLength = maxStringLength;

            switch (SortOrder)
            {
                case SortOrderMap.Ascending:
                case SortOrderMap.Descending:
                    MaxLength = sizeof(int) + Encoding.GetMaxByteCount(MaxStringLength) + 5 + 2 * MaxStringLength;
                    break;
                case SortOrderMap.None:
                    MaxLength = int.MaxValue;
                    break;
            }
        }

        public StringKeyMap(int maxStringLength, SortOrderMap sortOrder)
            : this(maxStringLength, sortOrder, CultureInfo.InvariantCulture.CompareInfo, Encoding.Unicode)
        {
        }

        public StringKeyMap(int maxStringLength)
            : this(maxStringLength, SortOrderMap.Ascending, CultureInfo.InvariantCulture.CompareInfo, Encoding.Unicode)
        {
        }

        public StringKeyMap()
            : this(DEFAULT_MAX_LENGTH)
        {
        }

        #region IKeyMap<string> Members

        public byte[] Direct(string key)
        {
            byte[] encoded = Encoding.GetBytes(key);

            if (SortOrder == SortOrderMap.None)
                return encoded;

            if (key.Length > MaxStringLength)
                throw new IndexOutOfRangeException(String.Format("key.Length > {0}", MaxStringLength));

            byte[] index = new byte[MaxLength];
            byte[] buffer = CompareInfo.GetSortKey(key).KeyData;

            if (buffer.Length > 5 + 2 * MaxStringLength)
                throw new IndexOutOfRangeException(String.Format("sizeof({0}) of type string + internal data > {1} bytes", key, MaxLength));

            //Array.Reverse(index);
            int count = buffer.Length / 2;
            for (int i = 0; i < count; i++)
            {
                byte tmp = buffer[i];
                buffer[i] = buffer[buffer.Length - i - 1];
                buffer[buffer.Length - i - 1] = tmp;
            }

            if (SortOrder == SortOrderMap.Descending)
            {
                for (int i = 0; i < buffer.Length; i++)
                    buffer[i] = (byte)(byte.MaxValue - buffer[i]);
            }

            buffer.CopyTo(index, index.Length - buffer.Length);
            BitConverter.GetBytes(encoded.Length).CopyTo(index, 0);
            encoded.CopyTo(index, sizeof(int));

            return index;
        }

        public string Reverse(byte[] index)
        {
            switch (SortOrder)
            {
                case SortOrderMap.Ascending:
                case SortOrderMap.Descending:
                    {
                        int length = BitConverter.ToInt32(index, 0);

                        return Encoding.GetString(index, sizeof(int), length);
                    }
                    break;
                case SortOrderMap.None:
                    {
                        return Encoding.GetString(index);
                    }
                    break;
                default:
                    throw new NotSupportedException(SortOrder.ToString());
            }
        }

        #endregion
    }

    [Serializable]
    public class StructKeyMap<TKey> : IKeyMap<TKey>
    //where TKey : struct
    {
        #region IKeyMap<TKey> Members

        public byte[] Direct(TKey key)
        {
            return StructurePrimitives.StructToByteArray(key);
        }

        public TKey Reverse(byte[] index)
        {
            return (TKey)StructurePrimitives.ByteArrayToStruct(index, typeof(TKey));
        }

        #endregion
    }

    [Serializable]
    public class LocatorKeyMap : IKeyMap<Locator>
    {
        private readonly StringKeyMap internalKeyMap = new StringKeyMap(0, SortOrderMap.None);

        #region IKeyMap<Locator> Members

        public byte[] Direct(Locator key)
        {
            return internalKeyMap.Direct(key.ToString());
        }

        public Locator Reverse(byte[] index)
        {
            return new Locator(internalKeyMap.Reverse(index));
        }

        #endregion
    }

    // Summary:
    //     Specifies how items in a list are sorted.
    public enum SortOrderMap
    {
        // Summary:
        //     The items are not sorted.
        None = 0,
        //
        // Summary:
        //     The items are sorted in ascending order.
        Ascending = 1,
        //
        // Summary:
        //     The items are sorted in descending order.
        Descending = 2,
    }
}
